package springcloud.test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import io.vavr.control.Try;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Mono;
import springcloud.test.common.ResultMessage;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.Callable;


/**
 * EurekaServerApplication
 */
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayTestApplication.class, args);
    }

    /**
     * 创建路由规则
     *
     * @param builder -- 路由构造器
     * @return 路由规则
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes() //开启路由配置
                // 匹配路径
                .route(f -> f.path("/user/**") // route方法逻辑是一个断言，后续会论述
                        // 转发到具体的URI
                        .uri("http://localhost:6001"))
                // 创建
                .build();
    }

    /**
     * 创建路由规则
     *
     * @param builder -- 路由构造器
     * @return 路由规则
     */
    @Bean
    public RouteLocator customRouteLocator2(RouteLocatorBuilder builder) {
        ZonedDateTime datetime = LocalDateTime.now()//获取当前时间
                // 两分钟后路由失效
                .plusMinutes(2)
                // 定义国际化区域
                .atZone(ZoneId.systemDefault()); // 定义UTC时间 ①
        return builder.routes()
                // 匹配
                .route("/user/**", r -> r.before(datetime) // 使用断言 ②
                        // 转发到具体的URI
                        .uri("http://localhost:6001"))
                // 创建
                .build();
    }


    // 注入Resilience4j限流器注册机
    @Autowired
    private RateLimiterRegistry rateLimiterRegistry = null;

    /**
     * 限流逻辑
     *
     * @return 是否放行结果
     */
    private ResultMessage rateLimit() {
        // 获取Resilience4j限速器
        RateLimiter userRateLimiter = rateLimiterRegistry.rateLimiter("user");
        // 绑定限速器
        Callable<ResultMessage> call
                = RateLimiter.decorateCallable(userRateLimiter,
                () -> new ResultMessage(true, "PASS")); // ①
        // 尝试获取结果
        Try<ResultMessage> tryResult = Try.of(() -> call.call())
                // 降级逻辑
                .recover(ex -> new ResultMessage(false, "TOO MANY REQUESTS")); // ②
        ResultMessage result = tryResult.get();
        return result;
    }

    /**
     * 自定义Gateway过滤器
     *
     * @return Gateway过滤器
     */
    private GatewayFilter customGatewayFilter() {
        return (exchange, chain) -> { // ①
            // 执行Resilience4j限速器逻辑
            ResultMessage resultMessage = rateLimit();
            if (!resultMessage.getSuccess()) { // 不放行逻辑 ②
                // 获取响应对象
                ServerHttpResponse response = exchange.getResponse();
                // 响应码为429——请求过多
                response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                // 响应类型为JSON
                response.getHeaders()
                        .setContentType(MediaType.APPLICATION_JSON_UTF8);
                // 转换为JSON字符串
                String body = toJson(resultMessage);
                // 响应数据放入缓冲区
                DataBuffer dataBuffer
                        = response.bufferFactory().wrap(body.getBytes());
                // 使用限流结果响应请求，不再继续执行过滤器链
                return response.writeWith(Mono.just(dataBuffer));
            }
            // 放行，继续执行过滤器链
            return chain.filter(exchange); // ③
        };
    }

    @Bean
    public RouteLocator customRouteLocator3(RouteLocatorBuilder builder) {
        return builder.routes()
                // 设置请求路径满足ANT风格“/user/**”的路由
                .route("user-service", r -> r.path("/user/**")
                        // 添加自定义过滤器
                        .filters(f -> f.filter(customGatewayFilter())) // ①
                        // 匹配路径
                        .uri("http://localhost:6001"))
                .build();
    }

    @Bean // 装配为Spring Bean
    public GlobalFilter tokenFilter() {
        // Lambda表达式
        return (exchange, chain) -> {
            // 判定请求头token参数是否存在
            boolean flag = !StringUtils.isBlank(
                    exchange.getRequest().getHeaders().getFirst("token"));
            if (flag) { // 存在，直接放行路由
                return chain.filter(exchange);
            }
            // 获取token参数
            String token = exchange.getRequest()
                    .getQueryParams().getFirst("token");
            ServerHttpRequest request = null;
            // token参数不为空，路由时将它放入请求头
            if (!StringUtils.isBlank(token)) {
                request  = exchange.getRequest().mutate() // 构建请求头
                        .header("token", token)
                        .build();
                // 构造请求头后执行责任链
                return chain.filter(exchange.mutate().request(request).build());
            }
            // 放行
            return chain.filter(exchange);
        };
    }

    /**
     * 将结果消息转换为JSON字符串
     *
     * @param result -- 结果消息
     * @return JSON字符串
     */
    private String toJson(ResultMessage result) {
        ObjectMapper mapper = new ObjectMapper();
        String body = null;
        try {
            body = mapper.writeValueAsString(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return body;
    }
}
